﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Forms.Integration;
using Silverlight.Hosting.Wpf.Private;
using Silverlight.Hosting.Wpf.Interop;
using System.IO;
using System.Runtime.InteropServices;
using System.Security;
using System.Windows.Interop;
using EventArgs = System.Windows.RoutedEventArgs;

namespace Silverlight.Hosting.Wpf
{
    public class SilverlightHost : WindowsFormsHost, ISilverlightHost
    {
        private XcpContainerProxy _container;
        private readonly static Type _ownerType = typeof(SilverlightHost);
        
        public SilverlightHost()
        {
            Child = (_container = new XcpContainerProxy(this));
        }

        #region Dependency Properties

        private static readonly DependencyPropertyKey IsWindowlessPropertyKey = DependencyProperty.RegisterReadOnly(
            "IsWindowless", typeof(bool), _ownerType, new PropertyMetadata(false));
        public static readonly DependencyProperty IsWindowlessProperty = IsWindowlessPropertyKey.DependencyProperty;

        private static readonly DependencyPropertyKey IsCreatedPropertyKey = DependencyProperty.RegisterReadOnly(
            "IsCreated", typeof(bool), _ownerType, new PropertyMetadata(false));
        public static readonly DependencyProperty IsCreatedProperty = IsCreatedPropertyKey.DependencyProperty;

        public static readonly DependencyProperty BaseUrlProperty = DependencyProperty.Register(
            "BaseUrl", typeof(Uri), _ownerType, new PropertyMetadata(null, OnBaseUrlChanged));

        public static readonly DependencyProperty HostOptionsProperty = DependencyProperty.Register(
            "HostOptions", typeof(XcpHostOptions), _ownerType, new PropertyMetadata(XcpHostOptions.Default, OnHostOptionsChanged));

        public static readonly DependencyProperty VersionProperty = DependencyProperty.Register(
            "Version", typeof(Version), _ownerType, new PropertyMetadata(new Version(4, 0), OnVersionChanged));

        public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(
            "Source", typeof(Uri), _ownerType, new PropertyMetadata(null, OnSourceChanged));

        public static readonly DependencyProperty XamlProperty = DependencyProperty.Register(
            "Xaml", typeof(string), _ownerType, new PropertyMetadata(null, OnXamlChanged));

        private static readonly DependencyPropertyKey IsSilverlightLoadedPropertyKey = DependencyProperty.RegisterReadOnly(
            "IsSilverlightLoaded", typeof(bool), _ownerType, new PropertyMetadata(false));
        public static readonly DependencyProperty IsSilverlightLoadedProperty = IsSilverlightLoadedPropertyKey.DependencyProperty;

        private static readonly DependencyPropertyKey ContentPropertyKey = DependencyProperty.RegisterReadOnly(
            "Content", typeof(Content), _ownerType, new PropertyMetadata());
        public static readonly DependencyProperty ContentProperty = ContentPropertyKey.DependencyProperty;

        private static readonly DependencyPropertyKey SettingsPropertyKey = DependencyProperty.RegisterReadOnly(
            "Settings", typeof(Settings), _ownerType, new PropertyMetadata());
        public static readonly DependencyProperty SettingsProperty = SettingsPropertyKey.DependencyProperty;
        
        public static readonly DependencyProperty InitParamsProperty = DependencyProperty.Register(
            "InitParams", typeof(string), _ownerType, new PropertyMetadata(null, OnInitParamsChanged));

        public static readonly DependencyProperty SplashScreenSourceProperty = DependencyProperty.Register(
            "SplashScreenSource", typeof(Uri), _ownerType, new PropertyMetadata(null, OnSplashScreenSourceChanged));

        public static readonly RoutedEvent CreatedEvent = EventManager.RegisterRoutedEvent(
            "Created", RoutingStrategy.Bubble, typeof(RoutedEventHandler), _ownerType);

        public static readonly RoutedEvent SilverlightLoadedEvent = EventManager.RegisterRoutedEvent(
            "SilverlightLoaded", RoutingStrategy.Bubble, typeof(RoutedEventHandler), _ownerType);

        public static readonly RoutedEvent SilverlightErrorEvent = EventManager.RegisterRoutedEvent(
            "SilverlightError", RoutingStrategy.Bubble, typeof(RoutedEventHandler<SilverlightErrorEventArgs>), _ownerType);

        public static readonly RoutedEvent SilverlightEventEvent = EventManager.RegisterRoutedEvent(
            "SilverlightEvent", RoutingStrategy.Bubble, typeof(RoutedEventHandler<SilverlightEventArgs>), _ownerType);

        public static readonly RoutedEvent SourceDownloadCompleteEvent = EventManager.RegisterRoutedEvent(
            "SourceDownloadComplete", RoutingStrategy.Bubble, typeof(RoutedEventHandler), _ownerType);

        public static readonly RoutedEvent SourceDownloadProgressChangedEvent = EventManager.RegisterRoutedEvent(
            "SourceDownloadProgressChanged", RoutingStrategy.Bubble, typeof(RoutedEventHandler<SourceDownloadProgressChangedEventArgs>), _ownerType);

        #endregion

        #region Public Events

        /// <summary>
        /// Raised when the Silverlight control is created.
        /// </summary>
        public event RoutedEventHandler Created
        {
            add { AddHandler(CreatedEvent, value); }
            remove { RemoveHandler(CreatedEvent, value); }
        }

        /// <summary>
        /// Raised whenever the XAML DOM has successfully loaded (i.e. the application
        /// is fully loaded and ready to display). This notification is useful because
        /// loading Silverlight can take longer for complex applications and when loading
        /// the CLR for the first time. 
        /// </summary>
        public event RoutedEventHandler SilverlightLoaded
        {
            add { AddHandler(SilverlightLoadedEvent, value); }
            remove { RemoveHandler(SilverlightLoadedEvent, value); }
        }

        /// <summary>
        /// Raised whenever errors are raised from within the XAML DOM of the hosted control.
        /// This event can indicate when a load failure has occurred, for example, when the
        /// expected .xap file does not exist. You can also use this event to respond to XAML
        /// parsing errors and runtime errors while using the JavaScript API for Silverlight.
        /// </summary>
        public event RoutedEventHandler<SilverlightErrorEventArgs> SilverlightError
        {
            add { AddHandler(SilverlightErrorEvent, value); }
            remove { RemoveHandler(SilverlightErrorEvent, value); }
        }

        /// <summary>
        /// Raised whenever events occur within the object model of the hosted control.
        /// </summary>
        public event RoutedEventHandler<SilverlightEventArgs> SilverlightEvent
        {
            add { AddHandler(SilverlightEventEvent, value); }
            remove { RemoveHandler(SilverlightEventEvent, value); }
        }

        /// <summary>
        /// Raised when the source download has finished.
        /// </summary>
        public event RoutedEventHandler SourceDownloadComplete
        {
            add { AddHandler(SourceDownloadCompleteEvent, value); }
            remove { RemoveHandler(SourceDownloadCompleteEvent, value); }
        }

        /// <summary>
        /// Raised when the source download progress changes.
        /// </summary>
        public event RoutedEventHandler<SourceDownloadProgressChangedEventArgs> SourceDownloadProgressChanged
        {
            add { AddHandler(SourceDownloadProgressChangedEvent, value); }
            remove { RemoveHandler(SourceDownloadProgressChangedEvent, value); }
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets a value indicating whether the control is windowless.
        /// </summary>
        public bool IsWindowless
        {
            get { return (bool)GetValue(IsWindowlessProperty); }
        }

        /// <summary>
        /// Gets a value indicating whether the control has been created.
        /// </summary>
        public bool IsCreated
        {
            get { return GetProperty(IsCreatedPropertyKey, _container.IsCreated); }
        }

        /// <summary>
        /// Gets or sets the URL that the hosted control should use as the base for any relative requests made for Source,
        /// to the downloader, etc.
        /// </summary>
        public Uri BaseUrl
        {
            get { return GetProperty(BaseUrlProperty, _container.BaseUrl); }
            set { SetValue(BaseUrlProperty, value); }
        }

        /// <summary>
        /// Gets or sets the XcpHostOptions specified by the host.
        /// </summary>
        public XcpHostOptions HostOptions
        {
            get { return GetProperty(HostOptionsProperty, _container.HostOptions); }
            set { SetValue(HostOptionsProperty, value); }
        }

        /// <summary>
        /// Gets or sets the version of the intalled Silverlight plug-in.
        /// </summary>
        [System.ComponentModel.TypeConverter(typeof(VersionConverter))]
        public Version Version
        {
            get { return GetProperty(VersionProperty, _container.Version); }
            set { SetValue(VersionProperty, value); }
        }


        /// <summary>
        /// Gets or sets the URI specified for the source content (can be a XAML page or a package, depending on
        /// which programming model is used for the Silverlight content).
        /// </summary>
        public Uri Source
        {
            get { return GetProperty(SourceProperty, _container.Source); }
            set { SetValue(SourceProperty, value); }
        }

        /// <summary>
        /// Gets or sets the XAML content dispayed in the Silverlight plug-in.
        /// </summary>
        public string Xaml
        {
            get { return GetProperty(XamlProperty, _container.Xaml); }
            set { SetValue(XamlProperty, value); }
        }

        /// <summary>
        /// Gets a Boolean that declares whether the control's onLoad event has been raised.
        /// </summary>
        public bool IsSilverlightLoaded
        {
            get { return GetProperty(IsSilverlightLoadedPropertyKey, _container.IsLoaded); }
        }

        /// <summary>
        /// Gets a Boolean that declares whether the control's onLoad event has been raised.
        /// </summary>
        bool ISilverlightHost.IsLoaded
        {
            get { return IsSilverlightLoaded; }
        }

        /// <summary>
        /// Gets the content sub-object.
        /// </summary>
        public Content Content
        {
            get { return GetProperty(ContentPropertyKey, _container.Content); }
        }

        /// <summary>
        /// Gets the settings sub-object.
        /// </summary>
        public Settings Settings
        {
            get { return GetProperty(SettingsPropertyKey, _container.Settings); }
        }

        /// <summary>
        /// Gets or sets initialization parameters.
        /// </summary>
        public string InitParams
        {
            get { return GetProperty(InitParamsProperty, _container.InitParams); }
            set { SetValue(InitParamsProperty, value); }
        }

        /// <summary>
        /// Gets or sets the URI specified for the splash screen source XAML.
        /// </summary>
        public Uri SplashScreenSource
        {
            get { return GetProperty(SplashScreenSourceProperty, _container.SplashScreenSource); }
            set { SetValue(SplashScreenSourceProperty, value); }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Loads the CLR runtime
        /// </summary>
        public void LoadRuntime()
        {
            _container.LoadRuntime();
        }

        /// <summary>
        /// Creates a new instance of a Silverlight helper object.
        /// </summary>
        /// <param name="objectType">Specifies the type of object to create.</param>
        /// <returns>The resulting object.</returns>
        public SafeComObject<IXcpObject> CreateObject(string objectType)
        {
            return _container.CreateObject(objectType);
        }

        /// <summary>
        /// Determines whether the existing Silverlight plug-in
        /// is a matching version or is a compatible version with a specified version string.
        /// </summary>
        /// <param name="version">String specifying the version to check.</param>
        /// <returns>True if the version is supported; otherwise, False.</returns>
        public bool IsVersionSupported(string version)
        {
            return _container.IsVersionSupported(version);
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Called by the hosted control whenever a download is requested. Hosts should implement this method to provide
        /// specialized download behavior for cases where the download might request cross-domain URLs, or where the host
        /// has other restrictions on requests made as a URI.
        /// </summary>
        /// <param name="url">The URL to download.</param>
        /// <param name="callback">The downloader callback implementation.</param>
        /// <param name="stream">The stream containing the downloaded content.</param>
        /// <returns>Returns OK if the download was handled, Pending if it is being done asynchronously, or Default
        /// if Silverlight should handle the download.</returns>
        protected virtual DownloadResult DownloadUrl(string url, IDownloadUrlCallback callback, out Stream stream)
        {
            return _container.InvokeBaseDownloadUrl(url, callback, out stream);
        }

        /// <summary>
        /// Called when the Silverlight control is created.
        /// </summary>
        protected virtual void OnCreated(EventArgs e)
        {
            _container.InvokeBaseOnCreated(e);

            e.RoutedEvent = CreatedEvent;
            e.Source = this;
            RaiseEvent(e);
        }

        /// <summary>
        /// Called whenever the XAML DOM has successfully loaded.
        /// </summary>
        protected virtual void OnSilverlightLoaded(EventArgs e)
        {
            _container.InvokeBaseOnSilverlightLoaded(e);

            e.RoutedEvent = SilverlightLoadedEvent;
            e.Source = this;
            RaiseEvent(e);
        }

        /// <summary>
        /// Called whenever errors are raised from within the XAML DOM of the hosted control.
        /// </summary>
        /// <param name="e">The details of the error.</param>
        protected virtual void OnSilverlightError(SilverlightErrorEventArgs e)
        {
            _container.InvokeBaseOnSilverlightError(e);

            e.RoutedEvent = SilverlightErrorEvent;
            e.Source = this;
            RaiseEvent(e);
        }

        /// <summary>
        /// Invoked whenever events occur within the object model of the hosted control.
        /// </summary>
        /// <param name="e">The details of the event.</param>
        protected virtual void OnSilverlightEvent(SilverlightEventArgs e)
        {
            _container.InvokeBaseOnSilverlightEvent(e);

            e.RoutedEvent = SilverlightEventEvent;
            e.Source = this;
            RaiseEvent(e);
        }

        /// <summary>
        /// Called when the source download has finished.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnSourceDownloadComplete(EventArgs e)
        {
            _container.InvokeBaseOnSourceDownloadComplete(e);

            e.RoutedEvent = SourceDownloadCompleteEvent;
            e.Source = this;
            RaiseEvent(e);
        }

        /// <summary>
        /// Called when the source download progress changes.
        /// </summary>
        /// <param name="e">The details of the download progress.</param>
        protected virtual void OnSourceDownloadProgressChanged(SourceDownloadProgressChangedEventArgs e)
        {
            _container.InvokeBaseOnSourceDownloadProgressChanged(e);

            e.RoutedEvent = SourceDownloadProgressChangedEvent;
            e.Source = this;
            RaiseEvent(e);
        }

        /// <summary>
        /// Called after the content in the Silverlight plug-in has completely loaded.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnContentLoaded(EventArgs e)
        {
            _container.InvokeBaseOnContentLoaded(e);
        }

        /// <summary>
        /// Called when when the Silverlight plug-in generates a XAML parse error or
        /// run-time error at the native-code level.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnContentError(ErrorEventArgs e)
        {
            _container.InvokeBaseOnContentError(e);
        }

        /// <summary>
        /// Called when the Silverlight plug-in content area receives a host-generated zoom event.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnContentZoomed(EventArgs e)
        {
            _container.InvokeBaseOnContentZoomed(e);
        }

        /// <summary>
        /// Called when the ActualHeight or ActualWidth of the Silverlight plug-in change.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnContentResized(EventArgs e)
        {
            _container.InvokeBaseOnContentResized(e);
        }

        /// <summary>
        /// Called whenever the FullScreen property of the Silverlight plug-in changes.
        /// </summary>
        /// <param name="e"></param>
        protected virtual void OnFullScreenChanged(EventArgs e)
        {
            _container.InvokeBaseOnFullScreenChanged(e);
        }

        protected override void OnKeyDown(KeyEventArgs e)
        {
            base.OnKeyDown(e);

            var msg = ComponentDispatcher.CurrentKeyboardMessage;

            DispatchMessage(msg);
            TranslateMessage(ref msg);
            e.Handled = (e.Key != Key.System);
        }

        protected override void OnKeyUp(KeyEventArgs e)
        {
            base.OnKeyUp(e);

            var msg = ComponentDispatcher.CurrentKeyboardMessage;

            DispatchMessage(msg);
            TranslateMessage(ref msg);
            e.Handled = (e.Key != Key.System);
        }

        protected override void Dispose(bool disposing)
        {
            Child = null;
            if (_container != null)
            {
                _container.Dispose();
                _container = null;
            }

            base.Dispose(disposing);
        }

        protected static void OnBaseUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as SilverlightHost)._container.BaseUrl = (Uri)e.NewValue;
        }

        protected static void OnHostOptionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as SilverlightHost)._container.HostOptions = (XcpHostOptions)e.NewValue;
        }

        protected static void OnVersionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as SilverlightHost)._container.Version = (Version)e.NewValue;
        }

        protected static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as SilverlightHost)._container.Source = (Uri)e.NewValue;

            // Update Xaml dependency property
            var xaml = (d as SilverlightHost).Xaml;
        }

        protected static void OnXamlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as SilverlightHost)._container.Xaml = (string)e.NewValue;

            // Update Source dependency property
            var source = (d as SilverlightHost).Source;
        }

        protected static void OnInitParamsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as SilverlightHost)._container.InitParams = (string)e.NewValue;
        }

        protected static void OnSplashScreenSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            (d as SilverlightHost)._container.SplashScreenSource = (Uri)e.NewValue;
        }

        #endregion

        #region Private Methods

        [DllImport("user32.dll")]
        private static extern bool TranslateMessage([System.Runtime.InteropServices.In] ref System.Windows.Interop.MSG lpMsg);

        [SuppressUnmanagedCodeSecurity]
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        private static extern IntPtr SendMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        private void DispatchMessage(System.Windows.Interop.MSG msg)
        {
            var m = System.Windows.Forms.Message.Create(msg.hwnd, msg.message, msg.wParam, msg.lParam);
            SendMessage(_container.Handle, (uint)msg.message, msg.wParam, msg.lParam);
        }

        private T GetProperty<T>(DependencyPropertyKey dpk, T value)
        {
            if (!object.Equals(value, GetValue(dpk.DependencyProperty)))
            {
                SetValue(dpk, value);
            }
            return value;
        }

        private T GetProperty<T>(DependencyProperty dp, T value)
        {
            if (!object.Equals(value, GetValue(dp)))
            {
                SetValue(dp, value);
            }
            return value;
        }

        #endregion

        #region ISilverlightHostEventProvider

        void ISilverlightHostEventProvider.OnCreated(EventArgs e)
        {
            // Update dependency properties
            var created = IsCreated;
            var content = Content;
            var settings = Settings;

            OnCreated(e);
        }

        void ISilverlightHostEventProvider.OnSilverlightLoaded(EventArgs e)
        {
            // Update IsSilverlightLoaded dependency property
            var loaded = IsSilverlightLoaded;
            
            OnSilverlightLoaded(e);
        }

        void ISilverlightHostEventProvider.OnSilverlightError(SilverlightErrorEventArgs e)
        {
            // Update IsSilverlightLoaded dependency property
            var loaded = IsSilverlightLoaded;
            
            OnSilverlightError(e);
        }

        void ISilverlightHostEventProvider.OnSilverlightEvent(SilverlightEventArgs e)
        {
            OnSilverlightEvent(e);
        }

        void ISilverlightHostEventProvider.OnSourceDownloadComplete(EventArgs e)
        {
            OnSourceDownloadComplete(e);
        }

        void ISilverlightHostEventProvider.OnSourceDownloadProgressChanged(SourceDownloadProgressChangedEventArgs e)
        {
            OnSourceDownloadProgressChanged(e);
        }

        void ISilverlightHostEventProvider.OnContentLoaded(EventArgs e)
        {
            // Update IsSilverlightLoaded dependency property
            var loaded = IsSilverlightLoaded;

            OnContentLoaded(e);
        }

        void ISilverlightHostEventProvider.OnContentError(ErrorEventArgs e)
        {
            // Update IsSilverlightLoaded dependency property
            var loaded = IsSilverlightLoaded;
            
            OnContentError(e);
        }

        void ISilverlightHostEventProvider.OnContentZoomed(EventArgs e)
        {
            OnContentZoomed(e);
        }

        void ISilverlightHostEventProvider.OnContentResized(EventArgs e)
        {
            OnContentResized(e);
        }

        void ISilverlightHostEventProvider.OnFullScreenChanged(EventArgs e)
        {
            OnFullScreenChanged(e);
        }

        DownloadResult ISilverlightHostEventProvider.DownloadUrl(string url, IDownloadUrlCallback callback, out Stream stream)
        {
            return DownloadUrl(url, callback, out stream);
        }

        #endregion

    }
}
